package com.ruoyi.framework.security.service;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.BlackListException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.WeChatMiniException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.UserAgent;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.config.WeChatMiniConfig;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginBody;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.security.WeChatMiniLoginBody;
import com.ruoyi.framework.web.client.WeChatMiniClient;
import com.ruoyi.framework.web.domain.Jscode2SessionResult;
import com.ruoyi.framework.web.domain.server.UserAgentInfo;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.domain.vo.TokenVo;
import com.ruoyi.project.system.service.ISysConfigService;
import com.ruoyi.project.system.service.ISysUserService;

import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 登录校验方法
 * 
 * @author ruoyi
 */
@Component
@Slf4j
public class SysLoginService
{
	protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

//    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
    
    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;
    
    @Autowired
    private SysPasswordService passwordService;
    
    @Autowired
    @Lazy
    private CaptchaService captchaService;

    @Autowired
    private WeChatMiniConfig weChatMiniConfig;
    
    @Autowired
    private WeChatMiniClient weChatMiniClient;
    
    @Autowired
    private UserAgent userAgent;
    
    /**
     * 登录验证
     * @param loginBody
     * @return token
     */
    public TokenVo login(LoginBody loginBody)
    {
    	String username = loginBody.getUsername();
        // 验证码校验
        validateCaptcha(loginBody);
        // 登录前置校验
		loginPreCheck(username, loginBody.getPassword());
		
        // 用户验证
        SysUser user = userService.selectUserByUserName(username);
        if (user == null) {
            log.info("登录用户：{} 不存在.", username);
            throw new ServiceException("登录用户：" + username + " 不存在");
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户：{} 已被删除.", username);
            throw new ServiceException("对不起，您的账号：" + username + " 已被删除");
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", username);
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, "账号被停用"));
            throw new ServiceException("对不起，您的账号：" + username + " 已停用");
        }

        // 密码正确
        if (SecurityUtils.matchesPassword(loginBody.getPassword(), user.getPassword())) {
            String token = loginSuccessAndCreateToken(user,UserConstants.DEVICE_PC); 
        	return TokenVo.builder().token(token).build();
        }

        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
        
        //"对不起，您的密码不正确"
        passwordService.validate(user);
        throw new UserPasswordNotMatchException();

    }
    
    /**
     * 登录成功，创建token
     * @param user
     * @param device
     * @return
     */
    private String loginSuccessAndCreateToken(SysUser user , String device) {
    	StpUtil.login(user.getUserId(),device);
        StpUtil.getSession(true).set("userInfo", user);
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        recordLoginInfo(user.getUserId());
        return createToken(user);
    }
    
    /**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    private String createToken(SysUser user) {
    	LoginUser loginUser = createLoginUser(user);
    	SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
    	loginUser.setToken(tokenInfo.getTokenValue());
    	loginUser.setLoginDevice(tokenInfo.getLoginDevice());
    	loginUser.setLoginId(tokenInfo.getLoginId().toString());
    	loginUser.setLoginTime(System.currentTimeMillis());
    	loginUser.setExpireTime(loginUser.getLoginTime() + tokenInfo.getTokenTimeout() * MILLIS_SECOND);
    	setUserAgent(loginUser);
    	refreshToken(loginUser,(int)tokenInfo.getTokenTimeout());
		return loginUser.getToken();
	}

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser)
    {
        UserAgentInfo userAgentInfo = userAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr();
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIPLocal(ip));
        loginUser.setBrowser(userAgentInfo.getBrowserWithVersion());
        loginUser.setOs(userAgentInfo.getOperatingSystem());
        loginUser.setUserAgent(userAgentInfo.getUserAgent());
    }
    
    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser , int expireTime)
    {
        // 根据uuid将loginUser缓存
        String userKey = CacheConstants.getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

	public LoginUser createLoginUser(SysUser user)
    {
        return new LoginUser(user.getUserId(), user.getDeptId(), user /*,permissionService.getMenuPermission(user)*/);
    }

    /**
     * 校验验证码
     * 
     * @param username 用户名
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public void validateCaptcha(LoginBody loginBody)
    {
    	CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(loginBody.getCode());
        ResponseModel response = captchaService.verification(captchaVO);
        if (!response.isSuccess())
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginBody.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
//        boolean captchaEnabled = configService.selectCaptchaEnabled();
//        if (captchaEnabled)
//        {
//            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
//            String captcha = redisCache.getCacheObject(verifyKey);
//            redisCache.deleteObject(verifyKey);
//            if (captcha == null)
//            {
//                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
//                throw new CaptchaExpireException();
//            }
//            if (!code.equalsIgnoreCase(captcha))
//            {
//                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
//                throw new CaptchaException();
//            }
//        }
    }

    /**
     * 登录前置校验
     * @param username 用户名
     * @param password 用户密码
     */
    public void loginPreCheck(String username, String password)
    {
    	//密码错误重试次数
//    	passwordService.passwordRetryLimitExceed(username);
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

	/**
	 * 微信小程序登录
	 * 
	 * @param loginBody
	 * @return
	 */
	public TokenVo weChatMiniLogin(WeChatMiniLoginBody loginBody)
	{
		Jscode2SessionResult result = weChatMiniClient.jscode2Session(weChatMiniConfig.getAppid(),
				weChatMiniConfig.getSecret(), loginBody.getJscode());
		if (result == null || result.getOpenid() == null)
		{
			AsyncManager.me().execute(
					AsyncFactory.recordLogininfor(loginBody.getJscode(), Constants.LOGIN_FAIL, result.toString()));
			throw new WeChatMiniException();
		}
		String openid = result.getOpenid();
		SysUser user = userService.selectUserByOpenid(openid);
		if (user == null)
		{
			user = new SysUser(openid);
			userService.createUser(user);
		}
		String token = loginSuccessAndCreateToken(user, UserConstants.DEVICE_WECHATMINI); 
		return TokenVo.builder().token(token).build();
	}
	
	
}
